#pragma once

class LocatableObject;
class Unit;

class Spell : private WheelTimerOwner
{
public:
	static GErrorCode CanCastSpell(
		Unit* pCaster, LocatableObject* pTarget, int spellCastFlags,
		const SpellPrototype* pSpellProto, uint32 spellLevel,
		const vector3f& tgtEffPos, const std::string_view& args);
	static void CastSpell(
		Unit* pCaster, LocatableObject* pTarget, uint64 spellInstGuid,
		const SpellPrototype* pSpellProto, uint32 spellLevel,
		const vector3f& tgtEffPos, const std::string_view& args);
	static void Delete(Spell* pSpell);

	static GErrorCode ApplySpell(Unit* pCaster,
		Unit* pTarget, uint64 spellInstGuid, uint32 spellID,
		uint32 spellLevel, int32 effectIndexes, bool isInAura);
	static GErrorCode ResumeSpell(
		Unit* pReceiver, uint32 spellID, uint32 spellLevel,
		uint32 effectIndex, uint64 elapseTime, uint64 skipTime,
		const std::string_view& buffArgs);

	void Interrupt(SpellInterruptBy interruptType);

	void SaveEffectTable(size_t i, LuaTable t, LuaTable t1);
	void SaveEffectInstInfos(size_t i, LuaTable t);
	void SaveEffectBuffInfos(size_t i, LuaTable t);

	Unit* GetCaster() const { return m_pCaster; }
	ObjGUID GetTargetGuid() const { return m_targetGuid; }
	uint64 GetSpellInstGuid() const { return m_spellInstGuid; }
	const SpellPrototype* GetSpellProto() const { return m_pSpellProto; }
	const SpellLevelPrototype* GetSpellLevelProto() const { return m_pSpellLevelProto; }
	uint32 GetSpellLevel() const { return m_spellLevel; }
	const vector3f GetTgtEffPos() const { return m_tgtEffPos; }
	const std::string& GetArgs() const { return m_args; }

	SpellStageType GetSpellStage() const { return m_spellStage; }
	bool IsExclusive() const { return m_pSpellProto->siInfo->spellFlags.isExclusive; }
	bool IsDeletable() const { return m_refCounter == 0; }

	static uint64 NewSpellInstGuid() { return ++m_spellInstGuidSeed; }

	static const SpellLevelPrototype*
		GetSpellLevelProto(const SpellPrototype* pSpellProto, uint32 spellLevel) {
		return &pSpellProto->sliList[std::max(spellLevel, 1u) - 1];
	}

private:
	Spell(Unit* pCaster, LocatableObject* pTarget, uint64 spellInstGuid,
		const SpellPrototype* pSpellProto, uint32 spellLevel,
		const vector3f& tgtEffPos, const std::string_view& args);
	~Spell();

	void Chant();
	void Cast();
	void Channel();
	void Cleanup();
	void Stop();

	void CastSpell4HP();
	void CastSpell4MP();

	void StartSpell4Effects();
	void StopSpell4Effects();

	void AddCooldown();

	void LoadAllEffectInfoTables();
	void LoadEffectInfoTable(size_t i, const SpellLevelEffectInfo& sleiInfo);

	void ActivateSpellEffects(SpellStageType stage);
	void StartReferenceSpellEffects(size_t tgtSleiIdx);
	void StartSelectEffectTargets(size_t i, const SpellLevelEffectInfo& sleiInfo);
	void StartApplyEffect2Targets(size_t i, const SpellLevelEffectInfo& sleiInfo);

	void SendSyncChant() const;
	void SendSyncChannel() const;
	void SendSyncCleanup() const;
	void SendSyncStop() const;

	void SendSyncPacket(const INetPacket& pck) const;

	uint32 GetCastStageTime(SpellStageType stage) const;
	uint32 GetCastStageExtraTime(SpellStageType stage) const;

	bool IsNeedSync2Client() const {
		return m_pSpellProto->siInfo->spellFlags.isExclusive ||
			m_pSpellProto->siInfo->spellFlags.isSync2Client ||
			m_pSpellProto->siInfo->spellFlags.isSync2AllClient;
	}
	bool IsNeedSync2AllClient() const {
		return m_pSpellProto->siInfo->spellFlags.isExclusive ||
			m_pSpellProto->siInfo->spellFlags.isSync2AllClient;
	}

	const LuaRef& GetEffectTable(size_t i) const {
		return m_effectTableList[i];
	}

	void SelectTargetSelf(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetFriend(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetEnemy(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetFriends(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetEnemies(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetSelfOrFriend(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetFocusFriends(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetFocusEnemies(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetTeamMember(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetTeamMembers(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetSelfOrTeamMember(SPELL_SELECT_TYPE_ARGS) const;
	void SelectTargetFocusTeamMembers(SPELL_SELECT_TYPE_ARGS) const;

	void SelectTargetCircle(SPELL_SELECT_MODE_ARGS) const;
	void SelectTargetSector(SPELL_SELECT_MODE_ARGS) const;
	void SelectTargetRect(SPELL_SELECT_MODE_ARGS) const;

	void SelectTargetByMode(SPELL_SELECT_MODE_ARGS) const;
	vector3f CalcSelectTarget_offsetDir(const vector3f& tgtEffPos) const;
	const vector3f& CalcSelectTarget_tgtEffNewPos() const;

	uint64 CalcUniqueKey4EffectArgs(size_t i) const;

	static GErrorCode CanCastSpell4HP(
		Unit* pCaster, const SpellLevelPrototype* pSpellLevelProto);
	static GErrorCode CanCastSpell4MP(
		Unit* pCaster, const SpellLevelPrototype* pSpellLevelProto);

	static GErrorCode CanCastSpell4Position(
		Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel);
	static GErrorCode CanCastSpell4Script(
		Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel);
	static GErrorCode CanCastSpell4Target(LocatableObject* pTarget,
		Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel);
	static GErrorCode CanCastSpell4Effects(LocatableObject* pTarget,
		Unit* pCaster, const SpellPrototype* pSpellProto, uint32 spellLevel);

	virtual WheelTimerMgr *GetWheelTimerMgr();

	Unit* const m_pCaster;
	const ObjGUID m_targetGuid;
	const uint64 m_spellInstGuid;
	const SpellPrototype* const m_pSpellProto;
	const SpellLevelPrototype* const m_pSpellLevelProto;
	const uint32 m_spellLevel;
	const vector3f m_tgtEffPos;
	const std::string m_args;

	SpellStageType m_spellStage;
	size_t m_refCounter;
	bool m_isStoped;

	mutable vector3f m_tgtEffNewPos;
	std::vector<LuaRef> m_effectTableList;
	std::vector<std::unordered_set<ObjGUID>> m_effectTargetList;

	static uint64 m_spellInstGuidSeed;

	typedef void (Spell::*SpellTargetTypeSelector)(SPELL_SELECT_TYPE_ARGS) const;
	typedef void (Spell::*SpellTargetModeSelector)(SPELL_SELECT_MODE_ARGS) const;
	static const SpellTargetTypeSelector m_spellTargetTypeSelectors[];
	static const SpellTargetModeSelector m_spellTargetModeSelectors[];
};
